#define CANTIDAD_SIMBOLOS 256

#include "math.h"

#include "funciones.h"
#include "CompresorAritmetico.h"
#include "TablaDeFrecuencias.h"

namespace Toolkit
{

CompresorAritmetico::CompresorAritmetico()
{
	piso = 0;
	techo = ~0;
}

CompresorAritmetico::~CompresorAritmetico()
{
}

string CompresorAritmetico::comprimir(const string &stringAcomprimir)
{
	piso = 0;
	techo = ~0;
	manejadorBits.reset();
	TablaDeFrecuencias tablaCompresion;

	for (unsigned int i = 0; i < stringAcomprimir.size()-1; i++)
	{
		comprimirCaracter((unsigned char)stringAcomprimir[i], tablaCompresion);
	}

	comprimirUltimoCaracter((unsigned char)stringAcomprimir[stringAcomprimir.size()-1], tablaCompresion);

	return manejadorBits.getEmision();
}


void CompresorAritmetico::comprimirCaracter(const unsigned char &caracterAcomprimir,
											TablaDeFrecuencias &tablaFrecuencia)
{
	calcularRango(caracterAcomprimir, tablaFrecuencia, this->piso, this->techo);

	manejadorBits.procesar(techo, piso);
	tablaFrecuencia.aumentarFrecuencia(caracterAcomprimir);
}

void CompresorAritmetico::comprimirUltimoCaracter(const unsigned char &caracterAcomprimir,
											TablaDeFrecuencias &tablaFrecuencia)
{
	calcularRango(caracterAcomprimir, tablaFrecuencia, this->piso, this->techo);
	manejadorBits.procesar(piso, piso);
}

void CompresorAritmetico::calcularRango(const unsigned char simbolo, TablaDeFrecuencias &tablaFrecuencia,
										unsigned int &nuevoPiso, unsigned int &nuevoTecho) const
{
	double probaSimboloAnterior, probaSimbolo;
	tablaFrecuencia.calcularProbabilidad(simbolo, probaSimboloAnterior, probaSimbolo);

	unsigned int rango = techo - piso;
	unsigned int pisoAux = piso;

	nuevoPiso = ceil(pisoAux + (rango * probaSimboloAnterior) + (1 * probaSimboloAnterior));
	nuevoTecho = floor(pisoAux + (rango * probaSimbolo) + (1 * probaSimbolo)) - 1;
}

unsigned int CompresorAritmetico::obtenerTamanoPadding(const string infoComprimida) const
{
	int tamanoPadding = 0;
	unsigned char ultimoByte = infoComprimida[infoComprimida.size() -1];

	for (int j=0; j<8 ; ++j)
	{
		++tamanoPadding;
		bool bitJ_esimo = obtenerBitReves(ultimoByte, j);
		if (bitJ_esimo == 1)
			break;
	}

	return tamanoPadding;
}

void CompresorAritmetico::cargarVentana(unsigned int &ventana, const string fuente,
										const unsigned int cantidadBits, unsigned int &UltimoBitEmitido)
{
	unsigned int primerByteAemitir = floor( (UltimoBitEmitido )/8);
	unsigned int buffer = fuente[primerByteAemitir];
	unsigned int mascara = ((unsigned char)~0)>> (UltimoBitEmitido % 8);
	buffer &= mascara;
	buffer <<= 8;
	buffer |= (unsigned char)fuente [primerByteAemitir + 1];
	buffer <<= 8;
	buffer |= (unsigned char)fuente [primerByteAemitir + 2];
	buffer <<= 8;
	buffer |= (unsigned char)fuente [primerByteAemitir + 3];

	buffer >>= (sizeof(int)*8 - cantidadBits - ( UltimoBitEmitido % 8) );

	ventana |= buffer;
	UltimoBitEmitido += cantidadBits;
}


string CompresorAritmetico::descomprimir(const string stringAdescomprimir)
{
	piso = 0;
	techo = ~0;
	manejadorBits.reset();
	TablaDeFrecuencias tablaDescompresion;

	// Obtengo el largo en bits del string comprimido, sin contar el padding
	unsigned int tamanoInfoComprimida = stringAdescomprimir.size() * 8 - obtenerTamanoPadding(stringAdescomprimir);

	string resultadoDescomprimido = "";

	//TODO: ver si el string comprimido esta vacio, devuelvo un string vacio...
	//TODO: ver esto en el compresor...que hace cuando le paso un string vacio?
	if (stringAdescomprimir.size() == 0) return resultadoDescomprimido;

	// Carga inicial de datos en la ventana
	unsigned int bitsEmitidos = 32;
	unsigned int ultimoBitEmitido = 0;
	unsigned int ventana = 0;

	// Itero deslizando la ventana para ir descomprimiendo.
	while (ultimoBitEmitido < tamanoInfoComprimida || bitsEmitidos == 0)
	{
		//Cargo mas datos en la ventana
		if(bitsEmitidos != 0)
		{
			cargarVentana(ventana, stringAdescomprimir, bitsEmitidos, ultimoBitEmitido);
		}
		bitsEmitidos = descomprimirCaracter(tablaDescompresion, ventana, tamanoInfoComprimida, ultimoBitEmitido, resultadoDescomprimido);
	}

	return resultadoDescomprimido;
}

unsigned int CompresorAritmetico::descomprimirCaracter(	TablaDeFrecuencias &tablaDescompresion, unsigned int &ventana,
														unsigned int &tamanoInfoComprimida,	unsigned int &ultimoBitEmitido,
														string &resultadoDescomprimido)
{
	unsigned int bitsEmitidos, npiso, ntecho;

	unsigned char simboloObjetvo = buscarSimbolo(ventana, tablaDescompresion, npiso, ntecho);

	if (ultimoBitEmitido >= tamanoInfoComprimida && simboloObjetvo == '\0')
	{
		return 1;
	} else {
		resultadoDescomprimido += simboloObjetvo;
	}

	manejadorBits.procesar(ntecho, npiso);
	techo = ntecho;
	piso = npiso;
	bitsEmitidos = manejadorBits.compactarVentana(ventana);
	tablaDescompresion.aumentarFrecuencia(simboloObjetvo);

	return bitsEmitidos;
}

unsigned char CompresorAritmetico::buscarSimbolo (const unsigned int ventana, TablaDeFrecuencias &tabla, unsigned int &npiso, unsigned int &ntecho)
{
	 unsigned char primero = 0, ultimo = CANTIDAD_SIMBOLOS - 1;

	 while (primero <= ultimo)
	 {
		 unsigned char simbolo = (primero + ultimo) / 2;
		 calcularRango(simbolo, tabla, npiso, ntecho);

		 if ( (npiso < ventana) && (ntecho < ventana) )
		 {
			 primero = simbolo + 1;
		 }
		 else if ( (npiso > ventana) && (ntecho > ventana) )
			 ultimo = simbolo - 1;
		 else
		 {
			 return simbolo;
		 }
	 }

	 return 0;//TODO: tirar exception de no encontrar simbolo
}

}
